package be.riaan.dgp;

trait Lists2 {
  trait MyList[a]
  case class Cons[a](x: a, xs: MyList[a]) extends MyList[a] {
    val ctor = ((x:a, xs: MyList[a]) => Cons[a](x, xs))
  }
  case class Nil[a] extends MyList[a]
}

trait Term
  case class Name(name: String) extends Term
  case class LocalVal(name: String, typ: Option[Type], value: Term, scope: Term) extends Term

trait Type
  case class Inst(e: Term) extends Type
  
class Map {
  def term(e: Term): Term = e match {
    case LocalVal(n, t, v, s) => 
      LocalVal(n, option(typ,t), term(v), term(s))
    case _ => e
  }
  def typ(t: Type): Type = t match {
    case Inst(e) => Inst(term(e))
  }  
  def option[t,s](f: t=>s, o: Option[t]): Option[s] 
    = o match {
    case Some(obj) => Some(f(obj))
    case None      => None
  }  
  def terms(ts: List[Term]): List[Term] = {
    for(val t <- ts) yield term(t)
  }
}
trait TypeFun {
  trait TypeFun1 { type t1 }
  type tapp[t <: TypeFun1, a] = t{type t1=a}
}

trait Lists extends TypeFun{
  trait MyList extends TypeFun1 {def map[a](f: t1 => a): tapp[MyList, a]}
  case class Cons[a](x: a, xs: tapp[MyList, a]) extends MyList{type t1=a
    def map[b](f: a => b): tapp[MyList, b] = Cons(f(x), xs.map(f))
    val ctor = ((x:a, xs: tapp[MyList, a]) => Cons[a](x, xs))
  }
  case class Nil[a] extends MyList{type t1=a
    def map[b](f: a => b): tapp[MyList, b] = Nil()}
}

trait Company extends TypeFun with Lists {
  case class Company(depts: tapp[MyList, Dept])
  case class Dept(name: Name, mgr: Manager, subs: tapp[MyList, SubUnit])
  trait SubUnit
    case class PU(emp: Employee) extends SubUnit
    case class DU(dept: Dept) extends SubUnit

  def Employee(p: Person, s: Salary): Employee = new Employee{val pers=p; val sal=s}
  trait Employee{val pers: Person; val sal: Salary} // extends Person
  
  def Person(): Person = new Person
  class Person //name: Name, addr: Address)
  
  def Salary(a: Float): Salary = new Salary{val amt=a}
  trait Salary{val amt: Float}

  type Manager = Employee
  type Name = String
  type Address = String
}

object boilerplate  extends Company {
  trait CompanyTermFun {
    def company(c: Company): Company = Company(c.depts.map(dept))
    def dept(d: Dept): Dept = Dept(d.name, employee(d.mgr), d.subs.map(subunit))
    def employee(e: Employee): Employee = Employee(e.pers, salary(e.sal))
    def subunit(su: SubUnit) = su match { // more precise type: PU => PU /\ DU => DU
      case PU(emp) => PU(employee(emp))
      case DU(dpt) => DU(dept(dpt))
    }
    def salary(s: Salary): Salary
  }
  
  class SalaryIncrease(inc: Float) extends CompanyTermFun {
    def salary(s: Salary) = Salary(s.amt*(inc+1))
  }
}

object noBoilerplate extends Company {
  trait Typeable[a] {
    //def cast[b](implicit conv: b => Typeable[b]): Option[b]
  }
  
  def mkT[a](f: PartialFunction[a,a])(x: a): a = if(f.isDefinedAt(x)) f(x) else x
  
  // used to encode rank-2 type + type context Term t
  trait TermFun {
    def apply[t](x: t)(implicit conv: t => Term[t]): t
  }
  
  trait Term[a] extends Typeable[a] {
    def gmapT(mapper: TermFun): a
    def everywhere(f: TermFun)(implicit c: a => Term[a]): a = f(gmapT(new TermFun {
      def apply[t](x: t)(implicit cc: t => Term[t]): t = x.everywhere(f)}))
  }

  implicit def list2term(l: MyList): Term[MyList] = new Term[MyList] with Typeable[MyList] {
    def gmapT(f: TermFun)=l match { case Cons(x, xs) => Cons(f(x), f(xs)) case Nil() => Nil}
  }
  
  implicit def company2term(c: Company): Term[Company] = new Term[Company] with Typeable[Company] {
    def gmapT(f: TermFun)=Company(f(c.depts))
  }

  implicit def dept2term(d: Dept): Term[Dept] = new Term[Dept] with Typeable[Dept] {
    def gmapT(f: TermFun)=Dept(f(d.name), f(d.mgr), f(d.subs))
  }

  implicit def dept2term(su: SubUnit): Term[SubUnit] = new Term[SubUnit] with Typeable[SubUnit] {
    def gmapT(f: TermFun)=su match { case PU(emp) => PU(f(emp)) case DU(dept) => DU(f(dept)) }
  }
  
  implicit def person2term(p: Person): Term[Person] = new Term[Person] with Typeable[Person] {
    def gmapT(f: TermFun)=Person()
  }

  implicit def any2term[a](f: a): Term[a] = new Term[a] with Typeable[a] {
    def gmapT(m: TermFun)=f
  }
  
  
  implicit def salary2term(s: Salary): Term[Salary] = new Term[Salary] with Typeable[Salary] {
    def gmapT(f: TermFun)=Salary(f(s.amt))
  }
  
  implicit def employee2term(e: Employee): Term[Employee] = new Term[Employee] with Typeable[Employee] {
    def gmapT(f: TermFun)=Employee(f(e.pers), f(e.sal))
  }
  
  def increase(c: Company, inc: Float): Company = c.everywhere(new TermFun {
    def apply[t](x: t)(implicit cc: t => Term[t]): t = x match { 
      case s: Salary => Salary(s.amt*(1+inc)).asInstanceOf[t]
      case _ => x
    }
    })
}